/**
 * This file is part of Logisim-evolution.
 *
 * Logisim-evolution is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Logisim-evolution is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Logisim-evolution.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Original code by Carl Burch (http://www.cburch.com), 2011.
 * Subsequent modifications by:
 *   + Haute École Spécialisée Bernoise
 *     http://www.bfh.ch
 *   + Haute École du paysage, d'ingénierie et d'architecture de Genève
 *     http://hepia.hesge.ch/
 *   + Haute École d'Ingénierie et de Gestion du Canton de Vaud
 *     http://www.heig-vd.ch/
 *   + REDS Institute - HEIG-VD, Yverdon-les-Bains, Switzerland
 *     http://reds.heig-vd.ch
 * This version of the project is currently maintained by:
 *   + Kevin Walsh (kwalsh@holycross.edu, http://mathcs.holycross.edu/~kwalsh)
 */

package com.cburch.logisim.data;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import com.cburch.logisim.util.Cache;

/**
 * Represents an immutable rectangular bounding box. This is analogous to
 * java.awt's <code>Point</code> class, except that objects of this type are
 * immutable.
 */
public class Location implements Comparable<Location> {
  public static Location create(int x, int y) {
    int hashCode = 31 * x + y;
    Location loc = cache.get(hashCode);
    if (loc != null && loc.x == x && loc.y == y)
      return loc;
    Location ret = new Location(hashCode, x, y);
    cache.put(hashCode, ret);
    return ret;
  }

  public static Location parse(String value) {
    String base = value;

    value = value.trim();
    if (value.charAt(0) == '(') {
      int len = value.length();
      if (value.charAt(len - 1) != ')') {
        throw new NumberFormatException("invalid point '" + base + "'");
      }
      value = value.substring(1, len - 1);
    }
    value = value.trim();
    int comma = value.indexOf(',');
    if (comma < 0) {
      comma = value.indexOf(' ');
      if (comma < 0) {
        throw new NumberFormatException("invalid point '" + base + "'");
      }
    }
    int x = Integer.parseInt(value.substring(0, comma).trim());
    int y = Integer.parseInt(value.substring(comma + 1).trim());
    return Location.create(x, y);
  }

  private static final Cache<Location> cache = new Cache<>();
  private final int hashCode;

  public final int x, y;

  private Location(int hashCode, int x, int y) {
    this.hashCode = hashCode;
    this.x = x;
    this.y = y;
  }

  public int compareTo(Location other) {
    if (this.x != other.x)
      return this.x - other.x;
    else
      return this.y - other.y;
  }

  @Override
  public boolean equals(Object other_obj) {
    if (!(other_obj instanceof Location))
      return false;
    Location other = (Location) other_obj;
    return this.x == other.x && this.y == other.y;
  }

  public boolean equals(int x, int y) {
    return this.x == x && this.y == y;
  }

  public int getX() {
    return x;
  }

  public int getY() {
    return y;
  }

  @Override
  public int hashCode() {
    return hashCode;
  }

  public int manhattanDistanceTo(int x, int y) {
    return Math.abs(x - this.x) + Math.abs(y - this.y);
  }

  public int manhattanDistanceTo(Location o) {
    return Math.abs(o.x - this.x) + Math.abs(o.y - this.y);
  }

  // rotates this around (xc,yc) assuming that this is facing in the
  // from direction and the returned bounds should face in the to direction.
  public Location rotate(Direction from, Direction to, int xc, int yc) {
    int degrees = to.toDegrees() - from.toDegrees();
    while (degrees >= 360)
      degrees -= 360;
    while (degrees < 0)
      degrees += 360;

    int dx = x - xc;
    int dy = y - yc;
    if (degrees == 90) {
      return create(xc + dy, yc - dx);
    } else if (degrees == 180) {
      return create(xc - dx, yc - dy);
    } else if (degrees == 270) {
      return create(xc - dy, yc + dx);
    } else {
      return this;
    }
  }

  @Override
  public String toString() {
    return "(" + x + "," + y + ")";
  }

  public Location translate(Direction dir, int dist) {
    return translate(dir, dist, 0);
  }

  public Location translate(Direction dir, int dist, int right) {
    if (dist == 0 && right == 0)
      return this;
    if (dir == Direction.EAST)
      return Location.create(x + dist, y + right);
    if (dir == Direction.WEST)
      return Location.create(x - dist, y - right);
    if (dir == Direction.SOUTH)
      return Location.create(x - right, y + dist);
    if (dir == Direction.NORTH)
      return Location.create(x + right, y - dist);
    return Location.create(x + dist, y + right);
  }

  public Location translate(int dx, int dy) {
    if (dx == 0 && dy == 0)
      return this;
    return Location.create(x + dx, y + dy);
  }

  public interface At {
    public Location getLocation();
  }

  // Left before right, ties broken top before bottom, ties broken with hashcode
  // (same as default ordering using Location.compareTo() except hashcode).
  private static class Horizontal implements Comparator<At> {
    public int compare(At a, At b) {
      Location aloc = a.getLocation();
      Location bloc = b.getLocation();
      if (aloc.x != bloc.x)
        return aloc.x - bloc.x;
      else if (aloc.y != bloc.y)
        return aloc.y - bloc.y;
      else
        return a.hashCode() - b.hashCode();
    }
  }

  public static final Comparator<At> CompareHorizontal = new Horizontal();

  public static <T extends At> void sortHorizontal(List<T> list) {
    Collections.sort(list, CompareHorizontal);
  }

  // Top before bottom, ties broken left before right, ties broken with hashcode.
  private static class Vertical implements Comparator<At> {
    public int compare(At a, At b) {
      Location aloc = a.getLocation();
      Location bloc = b.getLocation();
      if (aloc.y != bloc.y)
        return aloc.y - bloc.y;
      else if (aloc.x != bloc.x)
        return aloc.x - bloc.x;
      else
        return a.hashCode() - b.hashCode();
    }
  }

  public static final Comparator<At> CompareVertical = new Vertical();

  public static <T extends At> void sortVertical(List<T> list) {
    Collections.sort(list, CompareVertical);
  }

  public static final Location ORIGIN = create(0, 0);
}
